/* -*-  Mode:C; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
/*
 * Copyright (c) 1999, 2000 Massachusetts Institute of Technology.
 *
 * This software is being provided by the copyright holders under the GNU
 * General Public License, either version 2 or, at your discretion, any later
 * version. (See the file `LICENSE' in the source distribution.)
 *
 * This software is provided "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The entire risk as
 * to the quality and performance of the program is with you. See the GNU
 * General Public License for more details.
 *
 * The name and trademarks of copyright holders may NOT be used in 
 * advertising or publicity pertaining to the software without specific, 
 * written prior permission. Title to copyright in this software and any 
 * associated documentation will at all times remain with copyright 
 * holders. See the file CMAUTHORS which should have accompanied this software 
 * for a list of all copyright holders. 
 * 
 * Particular files in this distribution may be derived from previously
 * copyrighted software. This copyright applies only to those changes made by
 * the copyright holders listed in the CMAUTHORS file.
 */
/*
 * An implementation of the Congestion Manager (CM) for Linux.
 * Fairly simple round-robin CM scheduler.
 */

#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <net/sock.h>
#include <net/cm.h>
#include <net/cm_rrsched.h>
#include <net/cm_debug.h>


/*
 * Initialize CM scheduler state when a new CM is instantiated 
 * for this destination (macroflow).  Called by cm_open().
 */
void cmrrs_init(cm_sched_t *cms)
{
        CM_DEBUG_ENTER();
	CM_DPRINTF(CM_DEBUG_FLOW_INIT,
		   "cm_debug:  initializing CM sched state in cmrrs_init\n");
	cms->state.lastflow = 0;
	cms->ops.notify = cmrrs_notify;
	cms->ops.schedule = cmrrs_schedule;
	cms->ops.delete_flow = cmrrs_delete_flow;
	cms->ops.query_share = cmrrs_query_share;
}

/*
 * Round-robin scheduling.  Return flow that's allowed to transmit to caller.
 */
inline cm_flow_t *cmrrs_sched(cm_sched_t *cms)
{
	cm_flow_t *cur;
	int i = 0;

	CM_DEBUG_EN(CM_DEBUG_SCHED);
	read_lock_bh(&cms->mflow->flows_lock);
	if (!cms->state.lastflow)
		cms->state.lastflow = cms->mflow->flows;
	cur = cms->state.lastflow;

	do {
		/*
		 * No flow on the list should be NULL.
		 */
		cur = cur->next ? cur->next : cms->mflow->flows;
		if (i++ > cms->mflow->nflows) {
			printk("rrsched state.lastflow is messed up\n");
			cur = cms->mflow->flows;
			break;
		}
	} while (cur->pending != CM_REQUEST_ACTIVE &&
			cur != cms->state.lastflow);

	cms->state.lastflow = cur;

	CM_DPRINTF(CM_DEBUG_SCHED, "cm_debug: leaving %s\n", __FUNCTION__);

	read_unlock_bh(&cms->mflow->flows_lock); 
	if (cur->pending != CM_REQUEST_ACTIVE) 
		return NULL;
	return cur;
}

/*
 * Schedule a transmission of up to 1 MTU by application callback.
 */
void cmrrs_schedule(cm_sched_t *cms)
{
	cm_macroflow_t *mflow = cms->mflow;
	cm_flow_t *flow=NULL;
	cm_congctl_t *cc = mflow->congctl;
	__u32 expires = 0;
	int srtt = 0;
/* UNUSED...
	cm_congctl_t *cc=mflow->congctl;
	struct sock *sk;
	struct cm_opt tp;
*/

	CM_DEBUG_EN(CM_DEBUG_SCHED);
	if (mflow->nflows > 0) {
		/* 
		 * rrsched tries its best to find a pending flow.
		 */
		flow = cmrrs_sched(mflow->sched);
		if (flow == NULL) /* no pending flows on mflow */
		{
		        CM_DPRINTF(CM_DEBUG_SCHED,
				   "cm_debug:  %s: no pending flows on %p\n",
				   __FUNCTION__, mflow);
			return;
		}
	} else 
		return;

	flow->pending = CM_REQUEST_GRANTED;

	/* schedule request timeout after 1 rtt */
	srtt = cc->state.srtt;
	/* CM_MAX_RTO overloaded here */
	if (srtt == 0) srtt = CM_MAX_RTO;
	expires = (__u32)jiffies + (__u32)10*srtt;
        if (!flow->cm_request_timer.prev) {
                flow->cm_request_timer.expires = (__u32)expires;
                add_timer(&flow->cm_request_timer);
        } else {
                if (time_before(flow->cm_request_timer.expires,(__u32)expires))
			mod_timer(&flow->cm_request_timer,(__u32)expires);
	}
	if (flow->cmapp_send != NULL) { 
		flow->cmapp_send(flow); /* can send up to "size" bytes */
	}
}

void cmrrs_notify(cm_sched_t *cms, cm_flow_t *flow, int nsent){
}

void cmrrs_delete_flow(cm_sched_t *cms, cm_flow_t *flow)
{
	if (cms->state.lastflow != flow)
		return;
	/*  
	 * If NULL will pick up on next iteration.
	 */
	cms->state.lastflow = flow->next; 
}

double cmrrs_query_share(cm_sched_t *cms, cm_flow_t *flow)
{
	int nflows = flow->mflow->nflows;
	double share = 1./nflows;
	int isharexten = share*10.;
	return(share);
}


void cmrrs_update_stats(cm_macroflow_t *mflow)
{
	/*
         * Do callback to application if requested.
         */
}
